﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Diagnostics;
using Communergy.Common;

namespace CurrentCostLibrary
{
    public class ReadingParser
    {
        /// <summary>
        /// Creates a CurrentCostOutput record from base reading date and a valid xml document
        /// </summary>
        public static EnergyMonitorOutput GetCurrentCostOutput(DateTime readingDate, string inputFromCurrentCost, bool useLocalTime)
        {
            EnergyMonitorOutput newCurrentCostOutput = new EnergyMonitorOutput();

            var msg = XElement.Parse(inputFromCurrentCost);            

            //get reading date time (based on current date and time from reading
            var dateTimeValue = string.Format("{0} {1}", readingDate.ToShortDateString(), msg.Element("time").Value);
            DateTime dateTime;
            if (DateTime.TryParse(dateTimeValue, out dateTime))
            {
                newCurrentCostOutput.ReadingDateTime = dateTime;
            }

            //meter date of birth
            var dsbNode = msg.Element("dsb");
            if (dsbNode != null)
            {
                var dsbValue = dsbNode.Value;
                int dsb;
                if (int.TryParse(dsbValue, out dsb))
                {
                    newCurrentCostOutput.DateOfBirth = readingDate.AddDays(-dsb);
                }
            }

            //meterId
            var meterIdNode = msg.Element("id");
            if (meterIdNode != null)
            {
                var meterIdValue = meterIdNode.Value;
                newCurrentCostOutput.MeterId = meterIdValue;
            }

            //source
            var srcNode = msg.Element("src");
            if (srcNode != null)
            {
                var srcValue = srcNode.Value;
                newCurrentCostOutput.Source = srcValue;
            }

            //temp
            var tempNode = msg.Element("tmpr");
            if (tempNode != null)
            {
                double temp;
                if (double.TryParse(tempNode.Value, out temp))
                {
                    newCurrentCostOutput.Temperature = temp;
                }
            }

            
            //create sensor records
            newCurrentCostOutput.SensorReadings = new List<EnergyMonitorSensorReading>();
            for (int sensorId = 0; sensorId < 10; sensorId++)
            {


                var sensorReading = new EnergyMonitorSensorReading { SensorId = sensorId };

                sensorReading.LiveReadings = new List<EnergyData>();
                var liveReading = GetCurrentCostLiveReading(readingDate, inputFromCurrentCost, useLocalTime);
                if (liveReading != null && liveReading.SensorId == sensorId)
                {
                    sensorReading.LiveReadings.Add(liveReading);
                }

                var history = GetCurrentCostHistory(readingDate, inputFromCurrentCost, sensorId);
                if (history != null)
                {
                    sensorReading.History = history;
                }

                newCurrentCostOutput.SensorReadings.Add(sensorReading);
            }
            return newCurrentCostOutput;
        }

        /// <summary>
        /// Gets a live reading from the data.
        /// </summary>
        /// <param Name="readingDate">Date reading was made</param>
        /// <param Name="inputFromCurrentCost">Current Cost XML</param>
        /// <returns>Appropriate data if present, null if not</returns>
        public static CurrentCostLiveReading GetCurrentCostLiveReading(DateTime readingDate, string inputFromCurrentCost, bool useLocalTime)
        {
            CurrentCostLiveReading newReading = new CurrentCostLiveReading();
            try
            {

                var xml = XElement.Parse(inputFromCurrentCost);
                if (xml != null)
                {
                    var msg = xml.Element("msg");
                    var dateTimeValue = string.Format("{0} {1}", readingDate.ToShortDateString(), xml.Element("time").Value);
                    var sensorTypeText = xml.Element("type").Value;
                    int sensorType;
                    if (int.TryParse(sensorTypeText, out sensorType))
                    {
                        newReading.SensorType = CurrentCostHelpers.GetSensorType(sensorType);
                    }


                    var sensorText = xml.Element("sensor").Value;
                    if (sensorText == null)
                    {
                        return null;
                    }
                    int sensorId;
                    if (int.TryParse(sensorText, out sensorId))
                   
                    {
                        var ch1 = xml.Element("ch1");
                        //see if a single data record
                        if (ch1 == null)
                        {
                            //drop out if not present, eg history
                            return null;
                        }
                        var wattsValue = ch1.Element("watts").Value;

                        DateTime dateTime;
                        if (DateTime.TryParse(dateTimeValue, out dateTime))
                        {
                            int watts;
                            if (int.TryParse(wattsValue, out watts))
                            {
                                newReading.SensorId = sensorId;

                                if (useLocalTime)
                                {
                                    dateTime = DateTime.Now;
                                }
                                newReading.ReadingDateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
                                
                                newReading.AverageWatts = watts;
                                newReading.Interval = new TimeSpan(0, 0, 6);
                                return newReading;
                            }
                        }
                    }
                }
            }
            catch (Exception)
            {

                return null;
            }
            return newReading;

        }


        public static EnergyMonitorHistory GetCurrentCostHistory(DateTime readingDate, string inputFromCurrentCost, int sensorIdToRead)
        {

            EnergyMonitorHistory newCurrentCostHistory = new EnergyMonitorHistory();
            var msg = XElement.Parse(inputFromCurrentCost);
            //var msg = xml.Element("msg");

            var dateTimeValue = string.Format("{0} {1}", readingDate.ToShortDateString(), msg.Element("time").Value);
            DateTime dateTime;
            if (DateTime.TryParse(dateTimeValue, out dateTime))
            {
                newCurrentCostHistory.ReadingDateTime = dateTime;
                //calculate "zero hour" from base date time
                //Hourly data is recorded in 2 hour blocks. The current block (h002 = 0-2 hours
                //from the present time is the time from now until the next odd hour.
                //For example, if the reading is taken at 7:15am, the current block (h002)
                //7am-9am. If the reading is taken 1 hour late, 8:15am, the current block is 
                //still 7am-9am. Only at 9:01 does it move to 9am-11am.


                var baseDateTime = GetBaseHourDateTime(dateTime);
                var zeroHour = GetZeroHour(dateTime);
                
                var hist = msg.Element("hist");
                if (hist != null)
                {
                    //TODO - sensor Id
                    var data = (from dataBlocks in  hist.Elements()
                                from item in dataBlocks.Elements()
                                where item.Value == sensorIdToRead.ToString()
                                select dataBlocks).FirstOrDefault() ;
                    if (data != null)
                    {
                        var sensorTypeText = hist.Element("type").Value;
                        int sensorType;
                        if (int.TryParse(sensorTypeText, out sensorType))
                        {
                            newCurrentCostHistory.SensorType = CurrentCostHelpers.GetSensorType(sensorType);
                        }

                        //read sensor id
                        var sensorText = data.Element("sensor").Value;
                        int sensorId;
                        if (int.TryParse(sensorText, out sensorId))
                        {
                            //read data

                            //sensor
                            newCurrentCostHistory.SensorId = sensorId;

                            //hourly
                            newCurrentCostHistory.HourlyReadings = GetHourlyReadingsFromHistory(zeroHour, data);

                            //daily
                            newCurrentCostHistory.DailyReadings = GetDailyReadingsFromHistory(baseDateTime, data);

                            //monthly data
                            DateTime baseReadingDateMonth = new DateTime(baseDateTime.Year, baseDateTime.Month, 1);
                            newCurrentCostHistory.MonthlyReadings = GetMonthlyReadingsFromHistory(baseReadingDateMonth, data);

                        }
                    }
                }
                else
                {
                    return null;
                }
            }
            return newCurrentCostHistory;
        }

        private static DateTime GetBaseHourDateTime(DateTime dateTime)
        {
            return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, 0, 0);
        }
        #region Pull out historical data as list

        public static List<EnergyData> GetHourlyReadingsFromHistory(DateTime baseDateTime, XElement data)
        {
            TimeSpan interval = new TimeSpan(0, 1, 0);
            var hourlyCoreData = from h in data.Elements()
                                 where h.Name.LocalName.StartsWith("h")
                                 select new
                                 {
                                     BaseHourOffset = (Convert.ToInt32(h.Name.LocalName.Substring(1, 3))),
                                     BaseKWh = Convert.ToDecimal(h.Value) / 2 //each hour value is half the recorded value
                                 };

            var hourlyFirst = from h in hourlyCoreData
                              select new EnergyData
                              {
                                  ReadingDateTime =DateTime.SpecifyKind( baseDateTime.AddHours(-h.BaseHourOffset), DateTimeKind.Utc),
                                  Interval = interval,
                                  AverageWatts = h.BaseKWh /1000
                              };
            //get the second set of hours, adding an hour to the previous reading, and using the same KWh
            var hourlySecond = (from h in hourlyFirst
                                select new EnergyData
                                {
                                    ReadingDateTime = h.ReadingDateTime.AddHours(1),
                                    Interval = interval,
                                    AverageWatts = h.AverageWatts
                                });
            //put the 2 queries together, and order by datetime
            var hourly = (hourlyFirst.Union(hourlySecond).OrderBy(h => h.ReadingDateTime)).ToList();

            return hourly;
        }

        public static List<EnergyData> GetDailyReadingsFromHistory(DateTime baseDateTime, XElement data)
        {
            TimeSpan interval = new TimeSpan(1, 0, 0);
            //daily data
            var dailyCoreData = from d in data.Elements()
                                where d.Name.LocalName.StartsWith("d")
                                select new
                                {
                                    BaseDayOffset = (Convert.ToInt32(d.Name.LocalName.Substring(1, 3))),
                                    BaseKWh = Convert.ToDecimal(d.Value)
                                };
            var dailyData = from d in dailyCoreData
                            orderby  d.BaseDayOffset descending
                            select new EnergyData
                            {
                                ReadingDateTime = DateTime.SpecifyKind(baseDateTime.AddDays(-d.BaseDayOffset).Date.AddHours(1), DateTimeKind.Utc), 
                                Interval = interval,
                                AverageWatts = d.BaseKWh / (decimal) interval.TotalHours * 1000
                            };
            return dailyData.ToList();
        }

        public static List<EnergyData> GetMonthlyReadingsFromHistory(DateTime baseDateTime, XElement data)
        {
            var monthlyCoreData = from m in data.Elements()
                                  where m.Name.LocalName.StartsWith("m")
                                  
                                  select new
                                  {
                                      BaseMonthOffset = (Convert.ToInt32(m.Name.LocalName.Substring(1, 3))),
                                      BaseKWh = Convert.ToDecimal(m.Value)
                                  };
            var monthlyData = from m in monthlyCoreData
                              let readingDateTime = DateTime.SpecifyKind(baseDateTime.AddMonths(-m.BaseMonthOffset).Date.AddHours(1), DateTimeKind.Utc)
                              let interval = readingDateTime.MonthTimeSpan() 
                              select new EnergyData
                              {
                                  ReadingDateTime = readingDateTime,
                                  Interval = interval,
                                  AverageWatts = m.BaseKWh / (decimal) interval.TotalHours * 1000
                              };
            return monthlyData.ToList();
        }


        
        #endregion

        //gets the CurrentCost zero hour
        public static DateTime GetZeroHour(DateTime readingDateTime)
        {
            var baseDateTime = GetBaseHourDateTime(readingDateTime);
            //get the hour of the reading
            var hour = readingDateTime.Hour;
            var modHour = hour % 2;

            var hoursToAdd = 1 + modHour;

            var zeroHour = baseDateTime.AddHours(hoursToAdd);
            return zeroHour;
        }
            
        /// <summary>
        /// Takes a List of CurrentCostOutput records and combines the Live and History data
        /// </summary>
        public List<CurrentCostOutput> CombineCurrentCostOutputs()
        {
            throw new System.NotImplementedException();
        }
    }
}
